//
// Transactions.cpp
// libprotea
// Tools Library for the Protea Project / Business Transaction functionality
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../defines.h"
#include "AppInterface.h"
#include "Transactions.h"
#include "../Database/Database.h"
#include "../Security/base64.h"
#include "../entry.h"
#include <time.h>
#include <stdarg.h>

#include "../defns.h"
using namespace ProteaApp1;

////////////////////////////////////////////////////////////////
//
//	XCAppErrorHandler Class
//
///////////////////////////////////////////////////////////////
XCAppErrorHandler::XCAppErrorHandler( XCAppInterface* owner, XCTransactionInfo* intrans, XCTransactionInfo* error_check ) {
	myowner = owner;
	errlist = NULL;

	error_check_trans = error_check;
	trans = intrans;
	buffercount = 0;
};

XCAppErrorHandler::~XCAppErrorHandler() {
	XCErrInfo* temp = errlist;
	while (temp) {
		XCErrInfo* deleteme = temp;
		temp = temp->next;
		delete deleteme->error;
		delete deleteme;
	};
};

void XCAppErrorHandler::Add( long errnum, error_severity severity, long field, long parmcount, ... ) {
	INFUNC( XCAppErrorHandler::Add, myowner );

	XCErrInfo* temp = new XCErrInfo;
	temp->severity = severity;
	temp->next = errlist;
	temp->sent = false;
	temp->error = new XCToolsError( errnum, field, buffercount );
	if (parmcount) {
		// Read our arguments
		va_list parmlist;
		va_start( parmlist, parmcount );
		for (int i=0; i<parmcount; i++)
			temp->error->AddParm( va_arg( parmlist, void* ) );
		va_end( parmlist );
	};

	// Formulate the description
	temp->error->MakeDescr( myowner );

	myowner->ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Signal error: %d, %s", errnum, temp->error->GetDescription() );

	errlist = temp;
	if (severity == SEV_CRITICAL) {
		Finish();
		RAISETOOLSERROR( 0 );
	};

	// Ensure that the end user hasn't aborted the transaction
	CheckAbort();

	OUTFUNC();
};


void XCAppErrorHandler::Add( XCToolsError& error ) {
	INFUNC( XCAppErrorHandler::Add, myowner );

	XCErrInfo* temp = new XCErrInfo;
	temp->severity = SEV_ERROR;
	temp->next = errlist;
	temp->sent = false;
	error.SetOrder( buffercount );
	temp->error = error.Copy();
	errlist = temp;

	// Ensure that the end user hasn't aborted the transaction
	CheckAbort();

	OUTFUNC();
};



void XCAppErrorHandler::SendErrorMessage( XCErrInfo* errormsg ) {
	myowner->TransResponseMsg(
		trans,
		errormsg->error->GetErrorNumber(),
		errormsg->error->GetDescription(),
		errormsg->error->GetOrder(),
		errormsg->error->GetField(),
		errormsg->severity
	);
	errormsg->sent = true;
};



bool XCAppErrorHandler::Check() {
	INFUNC( XCAppErrorHandler::Check, myowner );

	bool sent_errors = false;

	// Ensure that the end user hasn't aborted the transaction
	CheckAbort();

	XCErrInfo* temp = errlist;
	while (temp) {
		if (!temp->sent) {
			if (temp->severity > SEV_WARNING) {
				sent_errors = true;
				SendErrorMessage( temp );
			};
		};

		temp = temp->next;
	};

	// If we sent any errors, signal the calling function to abort
	if (sent_errors) {
		// Always mark the transaction as failed if we sent error or critical messages
		trans->SetSuccess( false );

		// Bail out
		RAISETOOLSERROR( 0 );
	};

	OUTFUNCRET( sent_errors );
};

bool XCAppErrorHandler::Finish() {
	INFUNC( XCAppErrorHandler::Finish, myowner );

	bool sent_errors = false;

	XCErrInfo* temp = errlist;
	while (temp) {
		if (!temp->sent) {
			if (temp->severity > SEV_WARNING)
				sent_errors = true;

			SendErrorMessage( temp );
		};
		temp = temp->next;
	};

	if (sent_errors) {
		// Always mark the transaction as failed if we sent error or critical messages
		trans->SetSuccess( false );
	};

	OUTFUNCRET( sent_errors );
};



void XCAppErrorHandler::Clear() {
	INFUNC( XCAppErrorHandler::Clear, myowner );

	XCErrInfo* temp = errlist;
	while (temp) {
		XCErrInfo* killme = temp;
		temp = temp->next;
		delete killme;
	};

	errlist = NULL;

	OUTFUNC();
};


long XCAppErrorHandler::Count() {

	long retval = 0;
	INFUNC( XCAppErrorHandler::Count, myowner );

	XCErrInfo* temp = errlist;
	while ( temp )
	{
		retval++;
		temp = temp->next;
	};

	OUTFUNCRET( retval );
};



// If the transaction has been aborted, then raise an error message
void XCAppErrorHandler::CheckAbort() {
	if (error_check_trans->GetAbort()) {
		// Signal failure
		trans->SetSuccess( false );

		// Bail out
		throw XCToolsError( 0, 0, 0 );
	};
};

////////////////////////////////////////////////////////////////
//
//	XCBufferList Class
//
///////////////////////////////////////////////////////////////
XCBufferList::XCBufferList() {
	buffercount = 0;
	list_head = NULL;
	list_tail = NULL;
};

XCBufferList::~XCBufferList() {
	Clear();
};

void XCBufferList::Add( XCBuffer* new_buf ) {
	INFUNC( XCBufferList::Add, NULL );

	// Determine if we should add at the head or tail
	if (!list_head) {
		list_head = list_tail = new XCBufferPtr();
	} else {
		list_tail->next = new XCBufferPtr();
		list_tail = list_tail->next;
	};
	list_tail->buffer = new_buf;
	list_tail->next = NULL;

	buffercount++;

	OUTFUNC();
};

/// Remove a buffer from the list
XCBuffer* XCBufferList::Remove( int index ) {
	XCBuffer* result = NULL;

	INFUNC( XCBufferList::Remove, NULL );

	XCBufferPtr* temp = list_head;
	XCBufferPtr* deleteme = NULL;

	if (index == 0) {
		// Remove first entry
		if (list_head) {
			if (list_head == list_tail)
				list_tail = NULL;
			list_head = list_head->next;

			deleteme = temp;
		} else
			RAISETOOLSERROR( 1 );
	} else {
		// Remove middle entry

		// Search for entry previous to this one
		index--;
		while (index && temp) {
			temp = temp->next;
			index--;
		};

		// Remove the buffer entry
		if (temp->next) {
			deleteme = temp->next;
			temp->next = temp->next->next;
			if (list_tail == deleteme)
				list_tail = temp;
		} else
			RAISETOOLSERROR( 1 );
	};

	// Recover memory
	result = deleteme->buffer;
	deleteme->Release();

	buffercount--;

	OUTFUNCRET( result );
};

/// Remove a buffer from the list
void XCBufferList::Remove( XCBuffer* del_buffer ) {
	// Locate what index the buffer is located at and then remove by the index
	XCBufferPtr* temp = list_head;
	int index = 0;
	while (temp && temp->buffer != del_buffer) {
		temp = temp->next;
		index++;
	};

	if (temp)
		Remove( index );
};

void XCBufferList::Add( int count, ... ) {
	va_list args;
	va_start(args, count);
	for (int i=0; i<count; i++) {
		Add( va_arg( args, XCBuffer* ) );
	};
};

XCBuffer* XCBufferList::GetBuffer( int i ) {
	XCBufferPtr* temp = list_head;
	while (i && temp) {
		temp = temp->next;
		i--;
	};

	if (temp)
		return temp->buffer;
	else
		return NULL;
};

void XCBufferList::Clear() {
	XCBufferPtr* temp;
	while (list_head) {
		temp = list_head;
		list_head = list_head->next;
		temp->Release();
	};
	list_head = NULL;
	list_tail = NULL;
	buffercount = 0;
};






////////////////////////////////////////////////////////////////
//
//	XCTransactionInfo Class
//
///////////////////////////////////////////////////////////////
XCTransactionInfo::XCTransactionInfo( XCAppInterface* my_owner, XCMsgPort* port ) {
	transid = -1;
	trans_number = 0;
	success = false;
	abort = false;
	in_use = false;
	source = port;
	query_flag = false;
	job_flag = false;
	supertrans = NULL;
	supertransnum = 0;
	msglist = NULL;
	count = 0;
	totalcount = 0;
	next = NULL;
	owner = my_owner;
	user = NULL;
	pass = NULL;
	my_action = 0;
	my_phase = 0;
	header = NULL;
	lang = 0;
	record_count = 0;
	start_record = 0;
	gcs_trans = false;

	// For return messages only
	has_header = false;
	buffers_sent = false;
	message_count = 0;
	dest_port = NULL;
	reply = NULL;
};


XCTransactionInfo::~XCTransactionInfo()
{
	if (user) delete[] user;
	if (pass) {
		int len = strlen( pass );
		memset( pass, 0, len );
		delete[] pass;
	};

	// Clean up transaction list
	XCMessageList* list = msglist;
	XCMessageList* temp;
	while (list) {
		temp = list;
		list = list->next;
		if (temp)
		{
			delete temp;
		};
	};

	if (header)
		delete header;

//	if (credentials)
//		credentials->Release();
};



void XCTransactionInfo::SetReturnInfo( XCTransactionInfo* msg ) {
	dest_port = msg->dest_port;
	reply = msg->GetHeader();
};

void XCTransactionInfo::SetReturnInfo( XCMsgPort* dest, XCToolsMessage* msg ) {
	dest_port = dest;
	reply = msg;
};

void XCTransactionInfo::SetInputInfo( XCMsgPort* from ) {
	dest_port = from;
	reply = NULL;
};

void XCTransactionInfo::AddMessage( XCToolsMessage* msg, trans_data_type type ) {
	INFUNC( XCTransactionInfo::AddMessage, owner );

	// Set the buffers sent flag
	if (type == TRANS_HEADER_TYPE || type == TRANS_DETAIL_TYPE)
		buffers_sent = true;

	// Set the order number of the message
	trans_query_base* base = (trans_query_base*)msg->GetDataPtr();
	if (has_header) {
		if (type == TRANS_HEADER_TYPE) {
			// ERROR!!!  We already have a header buffer
		} else
			base->order = message_count;
	} else {
		// Always reserve order 0 for header buffer
		int order = message_count + 1;
		if (type == TRANS_HEADER_TYPE) {
			order = 0;
			has_header = true;
		};
		base->order = order;
	};

	// Increase the message count
	message_count++;

	// If responding directly, no need to mess with TransactionList
	if (reply) {
		XCMessageList response( msg );
		response.Send( this );
	} else {
		XCMessageList* temp;
		if (!msglist) {
			// First record
			msglist = lastmsg = temp = new XCMessageList( msg->Copy() );
			temp->next = NULL;
		} else {
			trans_query_base* info = (trans_query_base*)msg->GetDataPtr();
			long myorder = info->order;

			temp = msglist;
			if (((trans_query_base*)temp->msg->GetDataPtr())->order > myorder) {
				// Insert at beginning
				temp = new XCMessageList( msg->Copy() );
				temp->next = msglist;
				msglist = temp;
			} else {
				// See if it should be the last record...
				// This is an optimization - the record will most likely be the first, or the last
				if (((trans_query_base*)lastmsg->msg->GetDataPtr())->order < myorder) {
					temp = new XCMessageList( msg->Copy() );
					lastmsg->next = temp;
					temp->next = NULL;
					lastmsg = lastmsg->next;
				} else {  // Otherwise, it's somewhere in-between
					while (temp->next && ((trans_query_base*)temp->next->msg->GetDataPtr())->order < myorder)
						temp = temp->next;

					if (!temp->next) {
						// Append to the end
						temp->next = new XCMessageList( msg->Copy() );
						temp = temp->next;
						temp->next = NULL;
					} else {
						// Insertion
						XCMessageList* temp2 = new XCMessageList( msg->Copy() );

						temp2->next = temp->next;
						temp->next = temp2;
						temp = temp2;
					};
				};
			};
		};

		count++;
	};


	OUTFUNC()
};




void XCTransactionInfo::AddHeader( XCToolsMessage* msg, trans_data_type type ) {
	trans_query_base* base = (trans_query_base*)msg->GetDataPtr();

	if (type < QUERY_HEADER_INFO) {
		// Transaction specific things
		query_flag = false;
		trans_message* trans = (trans_message*)msg->GetDataPtr();

		SetTransNumber( trans->format );

		// Fill in information specific to the info message
		trans_info_message* imsg = (trans_info_message*)(trans->data);

		// Set Security
		if (imsg->userlen)
			SetUser( imsg->data );

		if (imsg->passlen)
			SetEncodedPass( imsg->data + imsg->userlen );

		// Super transaction information
		SetSuperID( imsg->trans_number );
		SetAction( imsg->action );
		SetSuccess( imsg->success != 0 );
		SetPhase( imsg->phase );
		SetLang( imsg->lang );
		SetSession( imsg->session_id );

		// Determine if this transaction was submitted online, or through a job
		base_message* msg_base = msg->GetHeaderPtr();
		if ((msg_base->miscflag & misc_job_txn) == misc_job_txn)
			SetJob();
	} else {
		// Query header specific things
		query_flag = true;
		sql_query_header_message* header = (sql_query_header_message*)msg->GetDataPtr();

		SetTransNumber( header->format );

		// Set Security
		if (header->userlen)
			SetUser( header->data );

		if (header->passlen)
			SetEncodedPass( header->data + header->userlen );

		SetRecordCount( header->record_count );
		SetStartRecord( header->start_record );
		SetAction( header->action );
	};

	// Set common items
	SetTotalCount( base->order );

	// Make a copy of the header message
	header = msg->Copy();
};




void XCTransactionInfo::SetSuccess( bool new_success ) {
	success = new_success;
};



void XCTransactionInfo::Release() {
	delete this;
};


long XCTransactionInfo::GetApplNumber() { 
	if (!gcs_trans)
		return owner->GetAppNumber(); 
	else
		return GCS_APP_NUMBER;
};


void XCTransactionInfo::SetUser( const char* value ) { 
	if ( user ) delete[] user;
	user = new char[ strlen( value ) + 1 ];
	strcpy( user, value ); 
};

void XCTransactionInfo::SetPass( const char* value, char xor_value ) { 
	if ( pass ) {
		int len = strlen( pass );
		memset( pass, 0, len );
		delete[] pass;
		pass = NULL;
	};

	// Create a copy of the value string
	int val_len = strlen( value );
	char* temp = new char[ val_len + 1 ];
	memcpy( temp, value, val_len+1 );

	// XOR the copy
	unsigned char* xtemp = (unsigned char*)temp;
	for (int i=0; i<val_len; i++) {
		*xtemp = *xtemp ^ xor_value;
		xtemp++;
	};

	// Do a base64 encoding
	XCBase64 base64;
	int new_len = (val_len * 4 / 3) + 4;
	pass = new char[ new_len ];
	base64.Encode( pass, (const unsigned char*)temp, val_len );
};

void XCTransactionInfo::SetEncodedPass( const char* value ) {
	if ( pass ) {
		int len = strlen( pass );
		memset( pass, 0, len );
		delete[] pass;
		pass = NULL;
	};
	pass = new char[ strlen( value ) + 1 ];
	strcpy( pass, value ); 
};

char* XCTransactionInfo::GetPass( char* password_buffer, char xor_value ) {

	// Do a base64 decoding
	if (pass) {
		int len = strlen( pass );
		XCBase64 base64;
		len = base64.Decode( pass, (unsigned char*)password_buffer, len );
		password_buffer[ len ] = 0;

		// Go through the xor process
		char* temp = password_buffer;
		for (int i=0; i<len; i++) {
			*temp = *temp ^ xor_value;
			temp++;
		};
	} else
		*password_buffer = 0;

	return password_buffer;
};

char* XCTransactionInfo::GetEncodedPass() {
	return pass;
};

bool XCTransactionInfo::IsQuery() {
	return query_flag;
};

void XCTransactionInfo::SetQuery() {
	query_flag = true;
};

void XCTransactionInfo::SetJob() {
	job_flag = true;
};

bool XCTransactionInfo::GetJob() {
	return job_flag;
};

////////////////////////////////////////////////////////////////
//
//	XCTransactionVars Class
//
///////////////////////////////////////////////////////////////
XCTransactionVars::XCTransactionVars( XCAppInterface* app, XCTransactionInfo* transaction, bool requires_header ) {

	INFUNC( XCTransactionVars::XCTransactionVars, app );

	int i;
	myapp = app;

	// Setup transaction result buffer
	if (transaction) {
		result = new XCTransactionInfo( app, transaction->GetSource() );
		result->SetTransID( transaction->GetTransID() );
		result->SetTransNumber( transaction->GetTransNumber() );
		result->SetReturnInfo( transaction );
		result->SetPhase( transaction->GetPhase() );
		result->SetLang( transaction->GetLang() );
		result->SetSession( transaction->GetSession() );

		// We are entering a transaction, so set the language code
		XCSetLanguage( transaction->GetLang() );

		app->ToolsTrace( TRC_APPINTERFACE, TLV_LOW, "Result = %x", result );

		// Setup credentials
		credentials = transaction->credentials;
		transaction->credentials = NULL;

		// Setup error handlers
		Errors = new XCAppErrorHandler( app, result, transaction );

		// Initialize buffers
		XCBuffer* tempbuf;
		buffer = NULL;
		detailcount = 0;
		detail = new XCBuffer*[ transaction->GetCount() ];
		XCMessageList* txn = transaction->GetFirstMessage();
		trans_message* input;

		transaction_defn* td = app->GetTransaction(	transaction->GetTransNumber() );

		while ( txn )
		{
			input = ( trans_message* )txn->msg->GetDataPtr();

			// Create the data buffer
			tempbuf = app->CreateDataBuffer( input->format, input->data );

			// Ensure that required fields have been entered
			tempbuf->CheckFieldLengths( Errors );

			// Do we want to fill in the default values?
			if (td && td->use_defaults)
				tempbuf->FillDefaults();

			if ( input->data_type == TRANS_HEADER_TYPE )
			{
				if ( buffer )
					RAISETOOLSERROR( ERR_MULT_HEADER_BUF );
				buffer = tempbuf;
			}
			else if ( input->data_type == TRANS_DETAIL_TYPE )
			{
				detail[ detailcount ] = tempbuf;
				detailcount++;
			}
			else
				RAISETOOLSERROR( ERR_INV_BUFFER_TYPE );
			txn = txn->next;
		};

		// Do we require a header for this transaction?
		if (!buffer && requires_header)
			RAISETOOLSERROR( ERR_MUST_HAVE_HEADER_BUF );

		// Set transaction phase
		phase = (int)transaction->GetPhase();
		id = transaction->GetTransID();

	} else {
		result = NULL;
		credentials = NULL;
		Errors = NULL;
		buffer = NULL;
		detailcount = 0;
		detail = NULL;

		// Go to default language
		XCSetLanguage( 0 );
	};

	// Setup query generator
	query = new XCSQLGen( app );

	// Initialize variables
	dbtxn = NULL;
	cleanresult = true;

	OUTFUNC();
};


XCTransactionVars::~XCTransactionVars() {
	myapp->CleanupObjects( 3, &buffer, &Errors, &query );
	if (cleanresult)
		myapp->CleanupObjects( 1, &result );

	if (credentials)
		credentials->Release();

	if (detail) {
		// Cleanup the detail buffers
		for (int i=0; i<detailcount; i++)
			myapp->CleanupObjects( 1, &detail[i] );

		// Cleanup the detail array
		delete [] detail;
	};
};


XCTransactionInfo* XCTransactionVars::ReturnResult() {
	cleanresult = false;
	return result;
};



////////////////
//
// XCMessageList
//
XCMessageList::~XCMessageList() {
	if (msg)
		delete msg;
};

void XCMessageList::Send( XCTransactionInfo* txn ) {
	// If this record has already been sent, don't send it again
	if (!sent) {
		trans_query_base* base = (trans_query_base*)msg->GetDataPtr();
		base->transid = txn->GetTransID();

		if ( txn->reply ) {
			msg->SetReply( *(txn->reply) );
		} else {
			if (txn->IsQuery())
				msg->SetHeader( msg_sql_query, src_int, dest_app, false, true );
			else
				msg->SetHeader( msg_trans, src_int, dest_app, false, true );
		};
		txn->owner->SendMessage( msg_src, txn->dest_port, msg );
		sent = true;
	};
};


////////////////////
//
// XCStatedTxn

XCStatedTxn::XCStatedTxn( long txnid, XCBufferList* newlist, XCAppInterface* newapp )
{
	active = true;
	id = txnid;
	starttime = time( NULL );
	buflist = newlist;
	next = NULL;
	App = newapp;
};

XCStatedTxn::~XCStatedTxn()
{
	if (buflist) {
		for (int i=0; i<buflist->buffercount; i++) {
			XCBuffer* buf = buflist->GetBuffer(i);
			if (buf)
				buf->Release();
		};
		buflist->Release(); 
	};
};

void XCStatedTxn::Reset()
{
	INFUNC(XCStatedTxn::Reset, App);
	starttime = time( NULL );
	OUTFUNC();
};

////////////////////
//
// XCStatedTxnList

XCStatedTxnList::~XCStatedTxnList()
{
	XCStatedTxn* temp;
	
	while ( list )
	{
		temp = list;
		list = list->next;
		if ( temp->buflist )
			temp->Release();
	};
}; 

XCStatedTxn* XCStatedTxnList::Add( long txnid, long count, ... ) {
	INFUNC(XCStatedTxnList::Add, App);

	// First, create the buffer list object to store the buffers
	XCBufferList* buflist = new XCBufferList();
	va_list args;
	va_start(args, count);
	for (int i=0; i<count; i++) {
		buflist->Add( va_arg( args, XCBuffer* ) );
	};

	// Next, create the actual stated txn storage class
	XCStatedTxn* temp = new XCStatedTxn( txnid, buflist, App );

	// Attempt to grab a lock
	cs.Lock();

	// Add ourselves to the front of the list
	temp->next = list;
	list = temp;

	App->trans_stated++;

	// Release our lock
	cs.UnLock();

	// "Release" the buffers that are being saved
	BufferRemove( buflist );

	App->ToolsTrace(TRC_APPINTERFACE, TLV_ALWAYS, "************** StatedTxnList->Add TXN_ID %ld, NODE %ld, BUF %ld, Ptr: %x", txnid, list, buflist, temp);
	OUTFUNCRET( temp );
};

void XCStatedTxnList::Remove( long txnid )
{
	INFUNC(XCStatedTxnList::Remove, App);

	cs.Lock();

	XCStatedTxn* prev = NULL;
	XCStatedTxn* temp = list;

	// Attempt to locate stated txn object
	while ( temp && temp->GetId() != txnid ) {
		prev = temp;
		temp = temp->next;
	};

	if ( temp ) {
		App->trans_stated--;
		if ( prev )
			prev->next = temp->next;
		else
			list = temp->next;
		temp->Release();
	};

	cs.UnLock();

	OUTFUNC();
};

XCStatedTxn* XCStatedTxnList::Find( long txnid )
{
	
	XCStatedTxn* retval = NULL;
	INFUNC(XCStatedTxnList::Find, App);
	XCStatedTxn* temp = list;
	if ( temp )
		App->ToolsTrace(TRC_APPINTERFACE, TLV_ALWAYS, "************** StatedTxnList->Find TXN_ID %ld, NODE %ld, BUF %ld", temp->GetId(), temp, temp->buflist);
	while ( temp && temp->GetId() != txnid ) 
	{
		App->ToolsTrace(TRC_APPINTERFACE, TLV_ALWAYS, "************** StatedTxnList->Find TXN_ID %ld, NODE %ld, BUF %ld", temp->GetId(), temp, temp->buflist);
		temp = temp->next;
	}
	if ( temp && temp->GetId() == txnid )
		retval = temp;
	OUTFUNCRET( retval );

};


void XCStatedTxnList::Cleanup() {
	const long TXN_TIMEOUT = 20*60;

	cs.Lock();

	int count = 0;
	int cleaned = 0;

	try {
		XCStatedTxn* prev = NULL;
		XCStatedTxn* temp = list;
		long current_time = time( NULL );

		while (temp) {
			count++;
			long diff = current_time - temp->starttime;
			if (diff > TXN_TIMEOUT) {
				if ( prev )
					prev->next = temp->next;
				else
					list = temp->next;

				// Move on to the next node, and delete the current node, but don't
				// change the previous pointer
				XCStatedTxn* to_delete = temp;
				temp = temp->next;
				delete to_delete;
				cleaned++;
			} else {
				// Just move on to the next node
				prev = temp;
				temp = temp->next;
			};
		};
	} catch( ... ) {
		//cs.UnLock();
		//throw;

		// We should handle the error better here, but as a temporary solution, 
		// abandon all existing stated transactions and bail
		//
		Protea::DebugOutput( "CLEANUP CAUGHT EXCEPTION" );
		list = NULL;
	};

	cs.UnLock();

//	printf( "\t\tSTATED TXN CLEANUP: %d out of %d\n", cleaned, count );
};
